﻿#region References
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;
using System.Threading;
using System.Text;
using Lidgren.Network;
using Ness.Engine.GUI;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Ness.Engine.Entities;
#endregion

namespace Ness.Engine.Net
{
    /// <summary>
    /// The main networking class for the server application
    /// </summary>
    class Server
    {
        #region Public variables
        public string id;
        public int port;
        public NetServer server;
        public bool enabled = true;
        #endregion

        #region Private variables
        private Thread thread;
        private Engine game;
        private double nextSendUpdates = NetTime.Now;
        private bool resendPlayerData;
        #endregion

        public Server(Engine game, string id, int port, int maxcon)
        {
            //Set some base variables
            this.id = id;
            this.port = port;
            this.game = game;

            //Initialise the main server instance
            Debugger.WriteColour("Starting server with identifier: " + id, ConsoleColor.Cyan);
            NetPeerConfiguration config = new NetPeerConfiguration(id);
            config.EnableMessageType(NetIncomingMessageType.DiscoveryRequest);
            config.Port = port;
            config.UseMessageRecycling = true;
            config.MaximumConnections = maxcon;

            server = new NetServer(config);

            //Start the server
            server.Start();

            //Start the server thread
            thread = new Thread(new ThreadStart(Run));
            thread.Start();
        }

        public void Run()
        {
            try
            {
                while (enabled)
                {
                    NetIncomingMessage msg;
                    while ((msg = server.ReadMessage()) != null)
                    {
                        switch (msg.MessageType)
                        {
                            case NetIncomingMessageType.DiscoveryRequest:
                                //Client wants to discover the server
                                server.SendDiscoveryResponse(null, msg.SenderEndpoint);
                                break;

                            case NetIncomingMessageType.VerboseDebugMessage:
                            case NetIncomingMessageType.DebugMessage:
                                Debugger.WriteColour(msg.ReadString(), ConsoleColor.Cyan);
                                break;

                            case NetIncomingMessageType.WarningMessage:
                            case NetIncomingMessageType.ErrorMessage:
                                Debugger.WriteError(msg.ReadString());
                                break;

                            case NetIncomingMessageType.Data:
                                if(!game.isDrawing) ParsePacket(msg);
                                break;

                            case NetIncomingMessageType.StatusChanged:
                                NetConnectionStatus status = (NetConnectionStatus)msg.ReadByte();
                                if (status == NetConnectionStatus.Connected)
                                {
                                    game.CreatePlayer(NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier), "models/sphere");

                                    Debugger.WriteColour(NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier) + " connected", ConsoleColor.Cyan);
                                    resendPlayerData = true;

                                    //New connection
                                    SendHandshake(msg.SenderConnection);
                                }

                                if (status == NetConnectionStatus.Disconnected)
                                {
                                    Debugger.WriteColour(NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier) + " disconnected.", ConsoleColor.Cyan);
                                    game.RemoveFromScene(game.playerList[NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier)] as Player);
                                    game.playerList.Remove(NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier));
                                }

                                break;
                        }
                    }

                    double now = NetTime.Now;
                    if (now > nextSendUpdates)
                    {
                        //Send player list
                        if (resendPlayerData)
                        {
                            Debugger.WriteColour("Sending player data", ConsoleColor.Cyan);
                            foreach (System.Collections.DictionaryEntry playerent in game.playerList)
                            {
                                if (playerent.Value is Player)
                                {
                                    //Create a new message
                                    NetOutgoingMessage om = server.CreateMessage();

                                    //Header
                                    om.Write("PLAYERDATA");

                                    //Player ID
                                    om.Write((playerent.Value as Player).playerID);

                                    //Player position
                                    om.Write((playerent.Value as Player).CharacterBody.Position.X);
                                    om.Write((playerent.Value as Player).CharacterBody.Position.Y);
                                    om.Write((playerent.Value as Player).CharacterBody.Position.Z);

                                    Serialize.Entity(playerent.Value as Player, om);

                                    foreach (NetConnection player in server.Connections)
                                    {
                                        //Send the message to the clients
                                     //   server.SendMessage(om, player, NetDeliveryMethod.ReliableOrdered);
                                    }
                                }
                            }
                            resendPlayerData = false;
                            Debugger.WriteColour("Done sending player data", ConsoleColor.Cyan);
                        }

                        //Set next update schedule. Stops overflow.
                        nextSendUpdates += (1.0 / 30.0);
                    }

                    //Sleep for other processes
                    Thread.Sleep(1);
                }

                thread.Abort();
            }
            catch(Exception e)
            {
                Debugger.WriteError(e.Message);
            }
        }

        public void Disconnect()
        {
            //Send disconnect message
            NetOutgoingMessage om = server.CreateMessage();

            //Header
            om.Write("BYE");

            foreach (NetConnection player in server.Connections)
            {
                //Send the message to the clients
                server.SendMessage(om, player, NetDeliveryMethod.ReliableOrdered);
            }

            //Stop the thread
            thread.Abort();
        }

        public void SendKillEntity(Entity ent)
        {
            //Create a new message
            NetOutgoingMessage om = server.CreateMessage();

            //Header
            om.Write("KILLENT");

            //Entity ID
            om.Write(ent.ToString());

            foreach (NetConnection player in server.Connections)
            {
                //Send the message to the clients
                server.SendMessage(om, player, NetDeliveryMethod.ReliableOrdered);
            }
        }

        private void SendHandshake(NetConnection player)
        {
            NetOutgoingMessage om = server.CreateMessage();

            om.Write("WELCUM");

            //Write player ID
            om.Write(NetUtility.ToHexString(player.RemoteUniqueIdentifier));

            //Write map name
            om.Write(game.currentMap);

            server.SendMessage(om, player, NetDeliveryMethod.ReliableOrdered);

            //Entity data
            foreach (Entity ent in game.serverEntities)
            {
                if ((ent as Player) == null)
                {
                    //Create a new message
                    NetOutgoingMessage entm = server.CreateMessage();

                    //Header
                    entm.Write("ENTDATA");

                    Serialize.Entity(ent, entm);

                    //Send the message to the client
                    server.SendMessage(entm, player, NetDeliveryMethod.ReliableOrdered);
                }
            }
        }

        public void SendEntity(Actor ent)
        {
            if ((ent as Player) != null)
            {
                //Don't send player entities
                return;
            }
            Debugger.WriteColour("Sending entity: " + ent + " " + (new Random()).Next(), ConsoleColor.Cyan);
            //Create a new message
            NetOutgoingMessage om = server.CreateMessage();

            //Header
            om.Write("ENTDATA");

            Serialize.Entity(ent, om);

            foreach (NetConnection player in server.Connections)
            {
                //if (player.Tag == null)
                //{
                    //Send the message to the client
                    server.SendMessage(om, player, NetDeliveryMethod.ReliableOrdered);
               // }
            }
        }

        private void ParsePacket(NetIncomingMessage msg)
        {
            string header = msg.ReadString();
            switch (header)
            {
                case "PLAYERPOS":
                    //Update player position

                    Ness.Engine.Entities.Entity plent = game.playerList[NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier)] as Ness.Engine.Entities.Entity;

                    //Get matrix data from message
                    /*if (!game.isDrawing) plent.worldMatrix.M11 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M12 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M13 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M14 = msg.ReadFloat();

                    if (!game.isDrawing) plent.worldMatrix.M21 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M22 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M23 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M24 = msg.ReadFloat();

                    if (!game.isDrawing) plent.worldMatrix.M31 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M32 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M33 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M34 = msg.ReadFloat();

                    if (!game.isDrawing) plent.worldMatrix.M41 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M42 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M43 = msg.ReadFloat();
                    if (!game.isDrawing) plent.worldMatrix.M44 = msg.ReadFloat();*/

                    SendCameraPosition(new Vector3(msg.ReadFloat(), msg.ReadFloat(), msg.ReadFloat()), NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier));
                    break;

                case "PLAYERKEY":
                    //Update player state by key
                    SendPlayerKey(msg.ReadString(), NetUtility.ToHexString(msg.SenderConnection.RemoteUniqueIdentifier));
                    break;

                case "HAI2U":
                    //Resend handshake
                    SendHandshake(msg.SenderConnection);
                    break;

                case "ENTDATA":
                    //Send entity list
                    foreach (Ness.Engine.Entities.Entity ent in game.serverEntities)
                    {
                        if ((ent as Player) == null)
                        {
                            //Create a new message
                            NetOutgoingMessage entMessage = server.CreateMessage();

                            //Header
                            entMessage.Write("ENTDATA");

                            Serialize.Entity(ent, entMessage);

                            server.SendMessage(entMessage, msg.SenderConnection, NetDeliveryMethod.ReliableOrdered);
                        }
                    }
                    break;

                default:
                    Debugger.WriteError("Unhandled header: " + header);
                    break;
            }
        }

        public void SendCameraPosition(Vector3 position, string pid)
        {
            //Update for game
            if ((game.playerList[pid] as Player).CharacterBody.noclip)
            {
                (game.playerList[pid] as Player).CharacterBody.Position += position;
            }
            else
            {
                (game.playerList[pid] as Player).CharacterBody.DesiredVelocity = position * 4;
            }

            resendPlayerData = true;
        }

        public void SendPlayerKey(string key, string pid)
        {
            //Check which key was pressed
            switch (key)
            {
                case "SPACE":
                    (game.playerList[pid] as Player).CharacterBody.DoJump();
                    break;

                case "NOCLIP":
                    (game.playerList[pid] as Player).CharacterBody.noclip = !((game.playerList[pid] as Player).CharacterBody.noclip);
                    break;
            }

            resendPlayerData = true;
        }

        public void SendLightData(Vector3 lightPosition, Color lightColor, Color lightSpecularColour, float lightHalfFalloffDistance, bool perPixel)
        {
            //Create a new message
            NetOutgoingMessage om = server.CreateMessage();

            //Header
            om.Write("LIGHTDATA");

            //Write data
            om.Write(lightPosition.X);
            om.Write(lightPosition.Y);
            om.Write(lightPosition.Z);

            om.Write(lightColor.R);
            om.Write(lightColor.G);
            om.Write(lightColor.B);

            om.Write(lightSpecularColour.R);
            om.Write(lightSpecularColour.G);
            om.Write(lightSpecularColour.B);

            om.Write(lightHalfFalloffDistance);
            om.Write(perPixel);

            foreach (NetConnection player in server.Connections)
            {
                //Send the message to the client
                server.SendMessage(om, player, NetDeliveryMethod.ReliableOrdered);
            }
        }
    }
}
